/*
 * File     : StructureArrayStructureField.java
 * Created  : 20 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import static com.googlecode.dni.internal.DniTypes.NATIVE_OBJECT_LIST_TYPE;
import static com.googlecode.dni.internal.DniTypes.POINTER_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.googlecode.dni.FixedSize;
import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.type.nativetype.Unsigned;
import com.googlecode.dni.type.structure.IllegalStructureException;
import com.googlecode.dni.type.structure.Structure;


/**
 * Encapsulates details of an arary of {@link Structure} (by value) structure's
 * field used during class generation.
 *
 * @author Matthew Wilson
 */
final class StructureArrayStructureField extends StructureField
{

    /** These annotations make no sense. */
    private static final List< Class< ? extends Annotation > > FORBIDDED_ANNOTATION_TYPES =
        new ArrayList< Class< ? extends Annotation > >();

    static
    {
        FORBIDDED_ANNOTATION_TYPES.add( Unsigned.class );
        FORBIDDED_ANNOTATION_TYPES.addAll( NATIVE_SIZED_TYPES );
    }

    private StructureRecord structureRecord;

    private String structureFactoryImplName;

    private int arrayLength;


    /**
     * {@inheritDoc}
     */
    @Override
    void setGetter( final Method getter )
    {
        super.setGetter( getter );

        checkAnnotations( FORBIDDED_ANNOTATION_TYPES );

        FixedSize fixedSize = getter.getAnnotation( FixedSize.class );
        if ( fixedSize == null )
        {
            throw new IllegalStructureException( "Structure array needs @FixedSize: " + getter );
        }
        this.arrayLength = fixedSize.value();

        // ensure the structure exists and is valid
        ParameterizedType parameterizedType = (ParameterizedType) getter.getGenericReturnType();
        Class< ? > type = (Class< ? >) parameterizedType.getActualTypeArguments()[ 0 ];
        Class< ? extends Structure > structureClass = type.asSubclass( Structure.class );
        try
        {
            this.structureRecord = getSession().getOrCreate( structureClass );
        }
        catch ( RecursiveStructureException exception )
        {
            throw new IllegalStructureException( "Infinitely recursive structures by value: " + getter, exception );
        }
        if ( this.structureRecord.getInfo().getSize() < 0 )
        {
            throw new IllegalStructureException( "Cannot use opaque structure by value: " + getter );
        }

        this.structureFactoryImplName = this.structureRecord.getFactoryImplName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    String getCType()
    {
        return "struct[" + this.arrayLength + "]";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getSize()
    {
        assert this.structureRecord != null;
        return this.structureRecord.getInfo().getSize() * this.arrayLength;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getPaddingUnitSize()
    {
        assert this.structureRecord != null;
        return this.structureRecord.getLargestFieldUnitSize();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    boolean expectsSetter()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void allocateFields( final ClassWriter classWriter,
                         final String implName,
                         final Map< String, StructureField > createdFields )
    {
        super.allocateFields( classWriter, implName, createdFields );

        String fieldName = "_" + getName();
        if ( createdFields.put( fieldName, this ) != null )
        {
            throw new AssertionError( "Duplicate structure field name: " + getName() );
        }

        classWriter.visitField( ACC_PRIVATE | ACC_FINAL,
                                fieldName,
                                "L" + NATIVE_OBJECT_LIST_TYPE + ";",
                                null,
                                null );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeConstructorFragment( final MethodVisitor methodVisitor,
                                   final String implName )
    {
        super.writeConstructorFragment( methodVisitor, implName );

        methodVisitor.visitVarInsn( ALOAD, 0 );
        methodVisitor.visitFieldInsn( GETSTATIC,
                                      this.structureFactoryImplName,
                                      "INSTANCE",
                                      "L" + this.structureFactoryImplName + ";" );

        writeGetPointer( methodVisitor );

        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       POINTER_TYPE,
                                       "fromAddress",
                                       "(J)L" + POINTER_TYPE + ";" );

        DniInternal.writeConstantInt( methodVisitor, this.arrayLength );

        methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                       this.structureFactoryImplName,
                                       "listFromPointer",
                                       "(L" + POINTER_TYPE + ";I)L" + NATIVE_OBJECT_LIST_TYPE + ";" );

        methodVisitor.visitFieldInsn( PUTFIELD,
                                      implName,
                                      "_" + getName(),
                                      "L" + NATIVE_OBJECT_LIST_TYPE + ";" );
    }

    // TODO v1.1 consider decent toString fragment

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        methodVisitor.visitVarInsn( ALOAD, 0 );
        methodVisitor.visitFieldInsn( GETFIELD,
                                      implName,
                                      "_" + getName(),
                                      "L" + NATIVE_OBJECT_LIST_TYPE + ";" );
        methodVisitor.visitInsn( Opcodes.ARETURN );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeSetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        // nothing
    }

}
